
import threading, time, ossl.util as util, ossl.websocket as websocket

def split(t, s):
    o = t.find(s)
    if o == -1: return (t, "")
    return (t[0:o], t[o+len(s):])

class Updater:
    
    ws = None
    retry_count = 0
    should_run = False
    is_connected = False

    def __init__(self):
        self.ws = None
        self.retry_count = 0
        self.should_run = False
        self.is_connected = False
        
    def start():
        if Updater.ws: Updater.stop()
        while Updater.ws: time.sleep(0.1) 
        print("Updater.start()")
        Updater.should_run = True
        Updater.ws = websocket.WebSocketApp( \
            "wss://rowlplay.net/blender/addons/ossl/download.ashx", \
            on_open=Updater.on_open, \
            on_message=Updater.on_message, \
            on_close=Updater.on_close)
        threading.Thread(target=Updater.thread, args=(1,)).start()
        print("Updater start() done")
    
    def stop():
        if not Updater.ws: return
        print("Updater.stop()")
        Updater.should_run = False
        Updater.ws.close()
        while Updater.ws: time.sleep(0.1)
        print("Updater.stop() done")

    def thread(name):
        print("update-thread started")
        Updater.ws.run_forever()
        Updater.ws = None
        print("update-thread ended, should retry =", Updater.should_run)
        if not Updater.should_run: return
        tm = 1.0 if Updater.retry_count == 0 else 10.0
        Updater.retry_count = Updater.retry_count + 1
        time.sleep(tm)
        Updater.start()

    def on_open(wsapp):
        Updater.is_connected = True
        Updater.retry_count = 0
        Updater.ws.send("verify")
        
    def on_close(wsapp, close_status_code, close_msg):
        Updater.is_connected = False
        print("Updater closed, should retry =", Updater.should_run)
        
    def on_message(wsapp, msg):
        if msg.startswith("UpdaterService sent: "):
            pf, msg = split(msg, ": ")
            msg, ts = split(msg, "\n")
            print(pf + ":", msg + "\n", ts)
            return
        cmd, msg = split(msg, "\n")
        
        if cmd == "verify":
            if msg == "":
                Updater.ws.send("verify")
                return
            if msg == "<error/>":
                print("update ossl-addon: error")
                return
            return verify(msg)
            
        print("*************", cmd, msg)

        if msg == "quit":
            Updater.should_run = False
            return Updater.stop()


def start():
    print("\nUpdater starting")
    Updater.start()
    print("\nUpdater started")
    
def stop():
    print("\nUpdater stopping")
    Updater.stop()
    print("\nUpdater stopped")



def verify(msg):
    print("************ verify *************")#\n", msg)
    try:
        import ossl, ossl.util as util
        from ossl.util import xml
        doc_store = xml.from_string(msg)
        if doc_store.getroot().tag == "error":
            print("update ossl-addon: error (2)")
            return
        f = open(util.path("addon_contents.xml"), "r")
        ft = f.read(-1)
        f.close()
        doc_local = xml.from_string(ft)
        
        #print(msg)
        #print(ft)
        
        changes = []
        get_changes(doc_local, get_iter(doc_local.getroot(), "d"), doc_store, get_iter(doc_store.getroot(), "d"), changes)
        get_changes(doc_local, get_iter(doc_local.getroot(), "f"), doc_store, get_iter(doc_store.getroot(), "f"), changes)

        chg = []
        rem = []
        for mode, index, elem in changes:
            #print(mode, elem.tag, [p.get("name") for p in index])
            if mode == "add" or mode == "chg": chg = chg + ["\\".join(p.get("n") for p in index)]
            if mode == "del": rem = rem + ["\\".join(p.get("n") for p in index)]

        if len(chg) == 0 and len(rem) == 0:
            print("update ossl-addon: no changes")
            return

        print("update ossl-addon: files to change:", chg)
        print("update ossl-addon: files to remove:", rem)
        
        url = util.addon_url() + "download.ashx"
        body = ""
        for c in chg: body = body + c + "\n"
        import requests
        r = requests.post(url + "?get_zip", body)
        print("update ossl-addon: return from " + url + "?get_zip", r.status_code)
        if r.status_code != 200: return
        import os, bpy, shutil
        dn = bpy.utils.user_resource("DATAFILES") + "\\addons"
        try: os.mkdir(dn)
        except: pass
        dn += "\\ossl"
        try: os.mkdir(dn)
        except: pass
        dn += "\\temp"
        try: os.mkdir(dn)
        except: pass
        try: shutil.rmtree(dn)
        except: pass
        try: os.mkdir(dn)
        except: pass
        f = open(dn + "\\ossl.zip", "wb")
        f.write(r.content)
        f.close()
        path = bpy.utils.user_resource("SCRIPTS") + "\\addons";
        print("unpack", "\n\tfrom: " + dn + "\\ossl.zip", "\n\t  to: " + path + "\\ossl")
        shutil.unpack_archive(dn + "\\ossl.zip", path + "\\ossl", "zip")
        for file in rem:
            print("update ossl-addon: removing " + path + "\\" + file)
            try: os.remove(path + "\\" + file)
            except: pass
        print("update ossl-addon: done\n")
        def timeout():
            import ossl.util as util
            util.reset_addon()
        from threading import Timer
        Timer(1, timeout).start()
        return
    except Exception as e:
        import traceback
        print(e, traceback.format_exc())

def get_iter(elem, type): return elem.findall("./" + type).__iter__()

def pth(doc, elem): return [] if elem is None else pth(doc, doc.parent(elem)) + [elem]

def add_change(ch, mode, doc, elem):
    ch.append((mode, pth(doc, elem), elem))

def get_changes(doc_old, it_old, doc_new, it_new, ch):
    try:
        import ossl.util as util
        from ossl.util import xml
        
        e_old = None if it_old is None else xml.next(it_old)
        e_new = None if it_new is None else xml.next(it_new)
        
        while e_old is not None or e_new is not None:

            if e_new is not None and ((e_old is None) or (e_new.get("n") < e_old.get("n"))):
                add_change(ch, "add", doc_new, e_new)
                if e_new.tag == "d":
                    get_changes(None, None, doc_new, get_iter(e_new, "d"), ch)
                    get_changes(None, None, doc_new, get_iter(e_new, "f"), ch)
                e_new = xml.next(it_new)
                continue

            if e_old is not None and ((e_new is None) or (e_old.get("n") < e_new.get("n"))):
                if e_old.tag == "d":
                    get_changes(doc_old, get_iter(e_old, "f"), None, None, ch)
                    get_changes(doc_old, get_iter(e_old, "d"), None, None, ch)
                add_change(ch, "del", doc_old, e_old)
                e_old = xml.next(it_old)
                continue

            if e_old is None:
                while e_new is not None:
                    add_change(ch, "add", doc_new, e_new)
                    if e_new.tag == "d":
                        get_changes(None, None, doc_new, get_iter(e_new, "d"), ch)
                        get_changes(None, None, doc_new, get_iter(e_new, "f"), ch)
                    e_new = xml.next(it_new)
                continue                    
                
            if e_new is None:
                while e_old is not None:
                    if e_old.tag == "d":
                        get_changes(doc_old, get_iter(e_old, "f"), None, None, ch)
                        get_changes(doc_old, get_iter(e_old, "d"), None, None, ch)
                    add_change(ch, "del", doc_old, e_old)
                    e_old = xml.next(it_old)
                continue                    
                
            if e_old.get("t") >= e_new.get("t") or e_old.tag == "d":
                add_change(ch, "===", doc_new, e_new)
            else:
                add_change(ch, "chg", doc_new, e_new)
            
            if e_old.tag == "d":
                get_changes(doc_old, get_iter(e_old, "d"), doc_new, get_iter(e_new, "d"), ch)
                get_changes(doc_old, get_iter(e_old, "f"), doc_new, get_iter(e_new, "f"), ch)

            e_old = xml.next(it_old)
            e_new = xml.next(it_new)

    except Exception as e:
        import traceback
        print(e, traceback.format_exc())
        return


def update():
    import bpy, os, requests, ossl.util as util
    doc_url = bl_info.get("doc_url")
    response = requests.get(doc_url, { "checkVersion" : "ossl" })
    if response.status_code != 200: return False
    dir = util.path("")
    print("path", dir)
    cwd = os.getcwd()
    os.chdir(dir)
    f = open("version_datetime", "r")
    t = f.read(1000)
    f.close()
    os.chdir(cwd)
    print("verdt", t)
    if t == response.text:
        print("\nossl is up-to-date – version:", response.text)
        return False
    download_dir = bpy.utils.user_resource('DATAFILES', "addons\\temp", create=True)
    download_file = download_dir + "\\ossl " + response.text + ".zip"
    download_link = os.path.dirname(doc_url) + "/ossl/ossl " + response.text + ".zip"
    print("download from ", download_link)
    print("download to ", download_file)
    receive = requests.get(download_link)
    print(download_link, "– status_code:", receive.status_code)
    with open(download_file, 'wb') as f: f.write(receive.content)
    
    try:
        bpy.utils._addon_utils.enable("ossl")
        bpy.utils._addon_utils._addon_remove("ossl")
    except Exception:
        print(Exception)
        pass
    bpy.ops.preferences.addon_install(overwrite=True, target='DEFAULT', \
        filepath=download_file, filter_folder=True, filter_python=True, filter_glob='*.py;*.zip')
    bpy.utils._addon_utils.enable("ossl")
    print("\n", "ossl addon has been updated to", response.text, "\n")
    return True


if __name__ == "__main__":
    print("\n")
    util.reset_addon()
